﻿using System;
using System.Collections;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Geometries;
using NetTopologySuite.Planargraph;
using NetTopologySuite.Planargraph.Algorithm;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Operation.Linemerge
{
	public class LineSequencer
	{
		public static bool IsSequenced(IGeometry geom)
		{
			if (!(geom is IMultiLineString))
			{
				return true;
			}
			IMultiLineString multiLineString = geom as IMultiLineString;
			HashSet<Coordinate> hashSet = new HashSet<Coordinate>();
			Coordinate coordinate = null;
			IList<Coordinate> list = new List<Coordinate>();
			for (int i = 0; i < multiLineString.NumGeometries; i++)
			{
				ILineString lineString = (ILineString)multiLineString.GetGeometryN(i);
				Coordinate coordinateN = lineString.GetCoordinateN(0);
				Coordinate coordinateN2 = lineString.GetCoordinateN(lineString.NumPoints - 1);
				if (hashSet.Contains(coordinateN))
				{
					return false;
				}
				if (hashSet.Contains(coordinateN2))
				{
					return false;
				}
				if (coordinate != null && !coordinateN.Equals(coordinate))
				{
					hashSet.UnionWith(list);
					list.Clear();
				}
				list.Add(coordinateN);
				list.Add(coordinateN2);
				coordinate = coordinateN2;
			}
			return true;
		}

		public void Add(IEnumerable<IGeometry> geometries)
		{
			foreach (IGeometry geometry in geometries)
			{
				this.Add(geometry);
			}
		}

		public void Add(IGeometry geometry)
		{
			geometry.Apply(new LineSequencer.GeometryComponentFilterImpl(this));
		}

		internal void AddLine(ILineString lineString)
		{
			if (this._factory == null)
			{
				this._factory = lineString.Factory;
			}
			this._graph.AddEdge(lineString);
			this._lineCount++;
		}

		public bool IsSequenceable()
		{
			this.ComputeSequence();
			return this._isSequenceable;
		}

		public IGeometry GetSequencedLineStrings()
		{
			this.ComputeSequence();
			return this._sequencedGeometry;
		}

		private void ComputeSequence()
		{
			if (this._isRun)
			{
				return;
			}
			this._isRun = true;
			IList<IEnumerable<DirectedEdge>> list = this.FindSequences();
			if (list == null)
			{
				return;
			}
			this._sequencedGeometry = this.BuildSequencedGeometry(list);
			this._isSequenceable = true;
			int numGeometries = this._sequencedGeometry.NumGeometries;
			Assert.IsTrue(this._lineCount == numGeometries, "Lines were missing from result");
			Assert.IsTrue(this._sequencedGeometry is ILineString || this._sequencedGeometry is IMultiLineString, "Result is not lineal");
		}

		private IList<IEnumerable<DirectedEdge>> FindSequences()
		{
			IList<IEnumerable<DirectedEdge>> list = new List<IEnumerable<DirectedEdge>>();
			foreach (Subgraph graph in new ConnectedSubgraphFinder(this._graph).GetConnectedSubgraphs())
			{
				if (!LineSequencer.HasSequence(graph))
				{
					return null;
				}
				IEnumerable<DirectedEdge> item = LineSequencer.FindSequence(graph);
				list.Add(item);
			}
			return list;
		}

		private static bool HasSequence(Subgraph graph)
		{
			int num = 0;
			IEnumerator nodeEnumerator = graph.GetNodeEnumerator();
			while (nodeEnumerator.MoveNext())
			{
				if (((Node)nodeEnumerator.Current).Degree % 2 == 1)
				{
					num++;
				}
			}
			return num <= 2;
		}

		private static IEnumerable<DirectedEdge> FindSequence(Subgraph graph)
		{
			GraphComponent.SetVisited(graph.GetEdgeEnumerator(), false);
			IEnumerator<DirectedEdge> enumerator = LineSequencer.FindLowestDegreeNode(graph).OutEdges.Edges.GetEnumerator();
			enumerator.MoveNext();
			DirectedEdge sym = enumerator.Current.Sym;
			LinkedList<DirectedEdge> linkedList = new LinkedList<DirectedEdge>();
			LinkedListNode<DirectedEdge> linkedListNode = LineSequencer.AddReverseSubpath(sym, null, linkedList, false);
			while (linkedListNode != null)
			{
				DirectedEdge directedEdge = LineSequencer.FindUnvisitedBestOrientedDE(linkedListNode.Value.FromNode);
				if (directedEdge != null)
				{
					linkedListNode = LineSequencer.AddReverseSubpath(directedEdge.Sym, linkedListNode, linkedList, true);
				}
				else
				{
					linkedListNode = linkedListNode.Previous;
				}
			}
			return LineSequencer.Orient(linkedList);
		}

		private static DirectedEdge FindUnvisitedBestOrientedDE(Node node)
		{
			DirectedEdge directedEdge = null;
			DirectedEdge result = null;
			foreach (DirectedEdge directedEdge2 in node.OutEdges)
			{
				DirectedEdge directedEdge3 = (DirectedEdge)directedEdge2;
				if (!directedEdge3.Edge.IsVisited)
				{
					result = directedEdge3;
					if (directedEdge3.EdgeDirection)
					{
						directedEdge = directedEdge3;
					}
				}
			}
			if (directedEdge != null)
			{
				return directedEdge;
			}
			return result;
		}

		private static LinkedListNode<DirectedEdge> AddReverseSubpath(DirectedEdge de, LinkedListNode<DirectedEdge> pos, LinkedList<DirectedEdge> list, bool expectedClosed)
		{
			Node toNode = de.ToNode;
			Node fromNode;
			for (;;)
			{
				if (pos == null)
				{
					pos = list.AddLast(de.Sym);
				}
				else
				{
					pos = list.AddAfter(pos, de.Sym);
				}
				de.Edge.Visited = true;
				fromNode = de.FromNode;
				DirectedEdge directedEdge = LineSequencer.FindUnvisitedBestOrientedDE(fromNode);
				if (directedEdge == null)
				{
					break;
				}
				de = directedEdge.Sym;
			}
			if (expectedClosed)
			{
				Assert.IsTrue(fromNode == toNode, "path not contiguous");
			}
			return pos;
		}

		private static Node FindLowestDegreeNode(Subgraph graph)
		{
			int num = int.MaxValue;
			Node node = null;
			IEnumerator<Node> nodeEnumerator = graph.GetNodeEnumerator();
			while (nodeEnumerator.MoveNext())
			{
				Node node2 = nodeEnumerator.Current;
				if (node == null || node2.Degree < num)
				{
					num = node2.Degree;
					node = node2;
				}
			}
			return node;
		}

		private static List<DirectedEdge> Orient(LinkedList<DirectedEdge> seq)
		{
			DirectedEdge value = seq.First.Value;
			DirectedEdge value2 = seq.Last.Value;
			Node fromNode = value.FromNode;
			Node toNode = value2.ToNode;
			bool flag = false;
			if (fromNode.Degree == 1 || toNode.Degree == 1)
			{
				bool flag2 = false;
				if (value2.ToNode.Degree == 1 && !value2.EdgeDirection)
				{
					flag2 = true;
					flag = true;
				}
				if (value.FromNode.Degree == 1 && value.EdgeDirection)
				{
					flag2 = true;
					flag = false;
				}
				if (!flag2 && value.FromNode.Degree == 1)
				{
					flag = true;
				}
			}
			if (flag)
			{
				return LineSequencer.Reverse(seq);
			}
			return new List<DirectedEdge>(seq);
		}

		private static List<DirectedEdge> Reverse(IEnumerable<DirectedEdge> seq)
		{
			return new List<DirectedEdge>(new Stack<DirectedEdge>(seq));
		}

		private IGeometry BuildSequencedGeometry(IEnumerable<IEnumerable<DirectedEdge>> sequences)
		{
			IList<IGeometry> list = new List<IGeometry>();
			foreach (IEnumerable<DirectedEdge> enumerable in sequences)
			{
				foreach (DirectedEdge directedEdge in ((IList<DirectedEdge>)enumerable))
				{
					ILineString line = ((LineMergeEdge)directedEdge.Edge).Line;
					ILineString item = line;
					if (!directedEdge.EdgeDirection && !line.IsClosed)
					{
						item = LineSequencer.Reverse(line);
					}
					list.Add(item);
				}
			}
			if (list.Count != 0)
			{
				return this._factory.BuildGeometry(list);
			}
			return this._factory.CreateMultiLineString(new ILineString[0]);
		}

		private static ILineString Reverse(ILineString line)
		{
			Coordinate[] coordinates = line.Coordinates;
			Array.Reverse(coordinates);
			ILineString lineString = line.Factory.CreateLineString(coordinates);
			lineString.UserData = line.UserData;
			return lineString;
		}

		private readonly LineMergeGraph _graph = new LineMergeGraph();

		private IGeometryFactory _factory = GeometryFactory.Default;

		private IGeometry _sequencedGeometry;

		private int _lineCount;

		private bool _isRun;

		private bool _isSequenceable;

		internal class GeometryComponentFilterImpl : IGeometryComponentFilter
		{
			internal GeometryComponentFilterImpl(LineSequencer sequencer)
			{
				this._sequencer = sequencer;
			}

			public void Filter(IGeometry component)
			{
				if (component is ILineString)
				{
					this._sequencer.AddLine(component as ILineString);
				}
			}

			private readonly LineSequencer _sequencer;
		}
	}
}
